/*
 * Copyright 2010 WANdisco, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.googlecode.svnj;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.svnj.callbacks.ChangePath;
import com.googlecode.svnj.callbacks.FileRevCallback;
import com.googlecode.svnj.callbacks.LocationSegment;
import com.googlecode.svnj.callbacks.LocationSegmentCallback;
import com.googlecode.svnj.callbacks.LogMessageCallback;
import com.googlecode.svnj.reports.Depth;

public class RepoContext {

    private static final Logger log = LoggerFactory.getLogger(RepoContext.class);
    
    // native pointer
    private long ptr;
    // URL path e.g., '/svn/myrepo'
    private String contextPath;
    // filesytem path e.g., '/svn/repo'
    private String repoDir;
    // revision we want to be rooted at
    private long rootRev;
    // fields from repo
    private long youngestRev;
    private String uuid;
    
    private static File svnParentPath;
    public static void init(File pp) {
        svnParentPath = pp;
    }
    public static final RepoContext findByPath(String appContextPath, String restOfPath,
            long rev) {
        RepoContext ret = null;
        p("findByPath(" + appContextPath + "," + restOfPath + "," + rev + ")");
        if (restOfPath.length() > 1 && restOfPath.charAt(0) == '/') {
            restOfPath = restOfPath.substring(1);
        }
        String subpath = restOfPath;
        int ind = restOfPath.indexOf('/');
        if (ind != -1) {
            subpath = subpath.substring(0, ind);
        }
        //p("subpath: '" + subpath + "'");
        File subdir = new File(svnParentPath, subpath);
        if (subdir.isDirectory()) {
            // FIXME: make sure it's a repo
            ret = new RepoContext(subdir,  appContextPath + "/" + subpath, rev);
            //p("returning " + ret);
        } else {
            p("not a directory: '" + subdir.getAbsolutePath() + "'");
        }
        return ret;
    }
    public RepoContext(File repo, String context) {
        this(repo, context, -1);
    }
    public RepoContext(File repo, String context, long rev) {
        this.contextPath = context;
        this.repoDir = repo.getAbsolutePath();
        p("repoDir '" + repoDir + "'");
        p("context '" + contextPath + "'");
        int len = context.length();
        if (len > 0 && context.charAt(len - 1) == '/') {
            context = context.substring(0, len - 1);
        }
        initSelf(repoDir, rev);
        this.rootRev = (rev == -1) ? this.getYoungestRev() : rev;
    }

    /**
     * 'http://host/svn/repo/sub/path' -> 'sub/path'
     * @param url
     * @return
     */
    public String subPathURL(String url) throws IOException {
        if (url != null) {
            // make sure it's not simply a path
            if (url.startsWith("http://") || url.startsWith("https://")) {
                URL u = new URL(url);
                return subPath(u.getFile());
            } else {
                return subPath(url);
            }
        }
        return null;
    }
    /** from a full URI path, return the relative path within the
     * repository, including the leading slash
     */
    public String subPath(String fullPath) {
        String ret = fullPath;
        if (fullPath.startsWith(contextPath)) {
            ret = fullPath.substring(contextPath.length());
            if (ret.length() > 0 && ret.charAt(0) == '/') {
                ret = ret.substring(1);
            }
        } else {
            throw new IllegalArgumentException(
                    "full path '" + fullPath + "' not a subpath of this repository ('"
                    + contextPath + "')"
                    );
        }
        
        return ret;
    }
    
    public DirEntry[] list(String subpath) {
        return list0(subpath);
    }
    // from node_kind enum
    public static final int ENTRY_NONE = 0;
    public static final int ENTRY_FILE = 1;
    public static final int ENTRY_DIR = 2;
    public boolean isFile(String path) {
        return nodeType(path) == ENTRY_FILE;
    }
    public boolean isDirectory(String path) {
        return nodeType(path) == ENTRY_DIR;
    }
    public boolean exists(String path) { return nodeType(path) > 0; }
    
    public Map<String,String> getPathProperties(String path, Depth depth) {
        Map<String,String> ret = new HashMap<String,String>();
        getPathProperties(path, depth.getValue(), ret);
        return ret;
    }
    public Map<String,String> getRevProperties(long rev) {
        Map<String,String> ret = new HashMap<String,String>();
        getRevProperties(rev, ret);
        return ret;
    }
    public String getContextPath() { return contextPath;  }
    /* path to repository directory */
    /*package*/ String getRepoDir() { return repoDir; }
    public long getRootRev() { return rootRev; }
    public long getYoungestRev() { return youngestRev; }

    public String getUuid() { return uuid; }

    /*
    SVN-Me-Resource: /svn/alphabet/!svn/me\r\n
    SVN-Rev-Root-Stub: /svn/alphabet/!svn/rvr\r\n
    SVN-Rev-Stub: /svn/alphabet/!svn/rev\r\n
    SVN-Txn-Root-Stub: /svn/alphabet/!svn/txr\r\n
    SVN-Txn-Stub: /svn/alphabet/!svn/txn     * 
     */
    /* special svn pseudo-file resources */
    public String getMeResource() {
        return contextPath + "/!svn/me";
    }
    public String getRevRootStub() {
        return contextPath + "/!svn/rvr";
    }
    public String getRevStub() {
        return contextPath + "/!svn/rev";
    }
    public String getTxnRootStub() {
        return contextPath + "/!svn/txr";
    }
    public String getTxnStub() {
        return contextPath + "/!svn/txn";
    }
    
    public String toString() {
        return "[RepoContext: path='" + contextPath + "' youngest="
        + youngestRev + " uuid=" + uuid + "]";
    }

    /** re-mount this repository at the specified revision */
    public void remount(long newrev) {
        if (newrev != this.rootRev) {
            log.info("* re-mounting, from " + rootRev + " to " + newrev);
            initSelf(this.repoDir, newrev);
            this.rootRev = (newrev == -1) ? this.getYoungestRev() : newrev;
        }
    }
    public native void cleanupSelf();
    public native String md5(String path);
    public native int writeContents(String path, OutputStream os) throws IOException;
    public native long getCreatedRev(String path);
    public native DirEntry getEntry(String path);
    public native void getLogs(
            LogMessageCallback callback,
            String[] paths, long startRev, long endRev, int limit,
            boolean discoverChangedPaths, boolean strictNodeHistory,
            boolean includeMergedRevs, String[] includeRevProps
            );
    public native void getFileRevs(String path, long startRevision, long endRevision,
            boolean includeMergedRevisions, FileRevCallback callback);
    public native void getLocationSegments(String path, long pegRevision,
            long startRevision, long endRevision, LocationSegmentCallback callback);
    
    private native void getRevProperties(long rev, Map map);
    private native void getPathProperties(String path, int depth, Map jmap);
    private static native void initFields();
    private native void initSelf(String repoPath, long rootRev);
    private native int nodeType(String path);
    private native DirEntry[] list0(String path);
    /* called from native code - DO NOT DELETE! */
    private static DirEntry createEntry(String fullpath, int type, long contentLen) {
        return new DirEntry(fullpath, type, contentLen);
    }
    public static class DirEntry {
        private boolean isDir;
        private String path;
        private long contentLength;
        DirEntry(String path, int type, long contentLen) {
            this.path = path;
            isDir = (type == ENTRY_DIR);
        }
        public String getPath() { return path; }
        public String getName() {
            int ind = path.lastIndexOf('/');
            return (ind != -1) ? path.substring(ind + 1) : path;
        }
        public boolean isDirectory() { return isDir; }
        public long getContentLength() { return contentLength; }
        public String toString() {
            return path;
        }
    }

    /* debug loading of dependent libs */
    private static final String[] DEP_LIBS = {
        "libapr-1", "libsvn_delta-1", "libsvn_fs-1",
        "libsvn_repos-1", "libsvn_subr-1",
        // svnjavahl dependencies on win32
        "MSVCRT", "MSVCP60", "libsvnjavahl-1",
        "svnjavahl-1"
    };
    static {
        if (!"true".equals(System.getProperty("svnj.nativeLoaded"))) {
            try {
                /*System.err.println("[RepoContext] PATH ENV='" + System.getenv("PATH") + "'");
                for (String lib : DEP_LIBS) {
                    System.loadLibrary(lib);
                    System.err.println("[RepoContext] loaded '" + lib + "' OK...");
                }*/
                System.loadLibrary("svnj");
                System.setProperty("svnj.nativeLoaded", "true");
                initFields();
            } catch (Error e) {
                e.printStackTrace();
                throw e;
            }
        }
    }
    public static void main(String[] a) throws Exception {
        p("running");
        RepoContext rc;
        rc = new RepoContext(
                new File("/home/dave/tmp/repo/with-branches"),
                "/svnj/with-branches");
        LocationSegmentCallback loc_cback = new LocationSegmentCallback() {

            public void singleSegment(LocationSegment segment) {
                p("singleSegment(" + segment + ")");
            }
            
        };
        rc.getLocationSegments("/branches/1.0", 4, 4, 0, loc_cback);
        //rc = new RepoContext(new File("/home/dave/tmp/repo/alphabet"), "/svnj/alphabet");
        /*Map<String,String> props = rc.getPathProperties("/a.txt", Depth.FILES);
        p("props: " + props);
        */
        /*
        FileRevCallback rev_cback = new FileRevCallback() {

            public void singleRevision(String path, long rev,
                    Map<String, String> revprops, boolean resultOfMerge,
                    Map<String, String> propDiffs, boolean expectDeltas) {
                p(path + "@" + rev + " rprops=" + revprops + ", propDiffs=" + propDiffs
                        + " deltas coming? " + expectDeltas);
                // check that C stack is blown out, and we return to java
                if (true) throw new RuntimeException("abort");
            }
            public void appendTextDelta(String deltaStr) throws Exception {
                //p("TX DELTA:" + deltaStr);
            }
            public void endTextDelta() throws Exception {
                p("end TX DELTA");
            }
            
        };
        rc.getFileRevs("/release-notes.html", 0, 8, false, rev_cback);
        LogMessageCallback cback = new LogMessageCallback() {

            @Override
            public void singleMessage(ChangePath[] paths, long rev, Map props, boolean chilluns) {
                p("logMsg: rev=" + rev + " props=" + props);
            }
            
        };
        String[] paths = new String[] { "another" };
        String[] revprops = new String[] { "svn:author", "foo" };
        revprops = null;
        rc.getLogs(cback, paths, 0, -1, 0, true, true, false, revprops);
        */
    }

    private static void p(String s) {
        System.err.println("[RepoContext] " + s);
        if (log.isDebugEnabled()) {
            log.debug(s);
        }
    }
}
